#include "ip.h"
#include "arp.h"
#include "icmp.h"
#include "udp.h"
#include <string.h>
#include <stdio.h>

uint16_t ip_id_count = 0;

/**
 * @brief 处理一个收到的数据包
 *        你首先需要做报头检查，检查项包括：版本号、总长度、首部长度等。
 * 
 *        接着，计算头部校验和，注意：需要先把头部校验和字段缓存起来，再将校验和字段清零，
 *        调用checksum16()函数计算头部检验和，比较计算的结果与之前缓存的校验和是否一致，
 *        如果不一致，则不处理该数据报。
 * 
 *        检查收到的数据包的目的IP地址是否为本机的IP地址，只处理目的IP为本机的数据报。
 * 
 *        检查IP报头的协议字段：
 *        如果是ICMP协议，则去掉IP头部，发送给ICMP协议层处理
 *        如果是UDP协议，则去掉IP头部，发送给UDP协议层处理
 *        如果是本实验中不支持的其他协议，则需要调用icmp_unreachable()函数回送一个ICMP协议不可达的报文。
 *          
 * @param buf 要处理的包
 */
void ip_in(buf_t *buf)
{
    // TODO 
    ip_hdr_t* ip_head = (ip_hdr_t*) buf->data;
    uint16_t checksum = ip_head->hdr_checksum;

    // 报头检查，版本号，首部长度（大于等于20字节），总长度，生存时间
    if(ip_head->version != IP_VERSION_4 // 只识别版本号为 IPv4的数据报
    || ip_head->hdr_len < sizeof(ip_hdr_t) / IP_HDR_LEN_PER_BYTE   // IP头部最小为20字节，必有ip_hdr_t的内容，首部长度为IP报头长度（位）/（单位4*字节8）
    || swap16(ip_head->total_len) > (buf->len)    // 包最后的00，可能使得total->len != buf->len，但total_len一定小于等于buf->len，否则数据丢失
    || ip_head->ttl < 0) {
        return;
    }
    // 计算校验和
    ip_head->hdr_checksum = 0; 
    ip_head->hdr_checksum = checksum16((uint16_t*)buf->data, ip_head->hdr_len * 2);     // 首部长度值1对应32位
    if(ip_head->hdr_checksum != checksum) {
        return;
    }
    // 目的地址为是否为本机
    if(memcmp(ip_head->dest_ip, net_if_ip, NET_IP_LEN) != 0) {
        return;
    }
    // 检查协议进行处理
    switch(ip_head->protocol) {
        case NET_PROTOCOL_ICMP:
            buf_remove_header(buf, ip_head->hdr_len*IP_HDR_LEN_PER_BYTE);
            icmp_in(buf, ip_head->src_ip);
            break;
        case NET_PROTOCOL_UDP:
            buf_remove_header(buf, ip_head->hdr_len*IP_HDR_LEN_PER_BYTE);
            udp_in(buf, ip_head->src_ip);
            break;
        default:
            icmp_unreachable(buf, ip_head->src_ip, ICMP_CODE_PROTOCOL_UNREACH);
            break;
    }
}

/**
 * @brief 处理一个要发送的分片
 *        你需要调用buf_add_header增加IP数据报头部缓存空间。
 *        填写IP数据报头部字段。
 *        将checksum字段填0，再调用checksum16()函数计算校验和，并将计算后的结果填写到checksum字段中。
 *        将封装后的IP数据报发送到arp层。
 * 
 * @param buf 要发送的分片
 * @param ip 目标ip地址
 * @param protocol 上层协议
 * @param id 数据包id
 * @param offset 分片offset，必须被8整除
 * @param mf 分片mf标志，是否有下一个分片
 */
void ip_fragment_out(buf_t *buf, uint8_t *ip, uint8_t protocol, int id, uint16_t offset, int mf)
{
    // TODO
    ip_hdr_t* ip_head = NULL;
    
    // 添加IP报头
    buf_add_header(buf, sizeof(ip_hdr_t));

    // 顺序填写报头
    ip_head = (ip_hdr_t*) buf->data;
    ip_head->version = IP_VERSION_4;
    ip_head->hdr_len = sizeof(ip_hdr_t) / IP_HDR_LEN_PER_BYTE;
    ip_head->tos = 0;
    ip_head->total_len = swap16(buf->len);
    ip_head->id = swap16(id);

    ip_head->flags_fragment = offset / IP_HDR_OFFSET_PER_BYTE;   // 偏移
    uint8_t* temp = (uint8_t*) &ip_head->flags_fragment;     // 标志位，小端存储，1存高字节，0存低字节
    temp[1] |= mf;    // 置mf为1
    ip_head->flags_fragment = swap16(ip_head->flags_fragment);

    ip_head->ttl = IP_DEFALUT_TTL;
    ip_head->protocol = protocol;

    memcpy(ip_head->src_ip, net_if_ip, NET_IP_LEN);
    memcpy(ip_head->dest_ip, ip, NET_IP_LEN);

    // 首部校验和需最后添加
    ip_head->hdr_checksum = 0;
    ip_head->hdr_checksum = checksum16((uint16_t*)buf->data, ip_head->hdr_len * 2);    // 首部长度值1对应32位

    arp_out(buf, ip, NET_PROTOCOL_IP);
}

/**
 * @brief 处理一个要发送的数据包
 *        你首先需要检查需要发送的IP数据报是否大于以太网帧的最大包长（1500字节 - ip包头长度）。
 *        
 *        如果超过，则需要分片发送。 
 *        分片步骤：
 *        （1）调用buf_init()函数初始化buf，长度为以太网帧的最大包长（1500字节 - ip包头头长度）
 *        （2）将数据报截断，每个截断后的包长度 = 以太网帧的最大包长，调用ip_fragment_out()函数发送出去
 *        （3）如果截断后最后的一个分片小于或等于以太网帧的最大包长，
 *             调用buf_init()函数初始化buf，长度为该分片大小，再调用ip_fragment_out()函数发送出去
 *             注意：最后一个分片的MF = 0
 *    
 *        如果没有超过以太网帧的最大包长，则直接调用调用ip_fragment_out()函数发送出去。
 * 
 * @param buf 要处理的包
 * @param ip 目标ip地址
 * @param protocol 上层协议
 */
void ip_out(buf_t *buf, uint8_t *ip, net_protocol_t protocol)
{
    // TODO 
    uint16_t max_len = ETHERNET_MTU - sizeof(ip_hdr_t);     // 最大数据包长
    uint16_t len = buf->len;    // 还未发送的数据包长
    uint16_t offset = 0;        // 偏移

    while(len > max_len) {
        // 分片，ip_buf截取数据报buf+offset，长度为max_len的数据报，发送
        buf_init(&txbuf, max_len);
        memcpy(txbuf.data, buf->data + offset, max_len);
        ip_fragment_out(&txbuf, ip, protocol, ip_id_count, offset, IP_MORE_FRAGMENT);
        len = len - max_len;
        offset = offset + max_len;
    }
    // 最后一个分片，mf=0
    buf_init(&txbuf, len);
    memcpy(txbuf.data, buf->data + offset, len);
    ip_fragment_out(&txbuf, ip, protocol, ip_id_count++, offset, 0);
}
